# 导入所需要的库
import json  # 导入json模块用于处理JSON格式数据的序列化和反序列化操作
import datetime  # 导入datetime模块用于处理日期和时间相关的操作
import hashlib  # 导入hashlib模块用于实现密码哈希加密，提供多种安全哈希算法
from prettytable import PrettyTable  # 从prettytable库导入PrettyTable类，用于创建美观的表格输出

# 系统数据存储容器 - 全局变量
user_registry = {}  # 用户注册表，字典类型，键为用户ID，值为用户基本信息
patient_database = {}  # 患者数据库，字典类型，存储患者的详细信息和预约记录
doctor_database = {}  # 医生数据库，字典类型，存储医生信息和出诊时间安排
booking_records = []  # 预约记录列表，数组类型，按时间顺序存储所有预约信息

# 数据持久化函数
def persist_to_file():
    """数据持久化到文件"""
    system_data = {  # 创建一个字典，将所有数据结构封装在一起，便于统一管理和存储
        'user_registry': user_registry,  # 将用户注册表数据添加到字典中
        'patient_database': patient_database,  # 将患者数据库添加到字典中
        'doctor_database': doctor_database,  # 将医生数据库添加到字典中
        'booking_records': booking_records  # 将预约记录列表添加到字典中
    }
    with open('hospital_system.json', 'w', encoding='utf-8') as data_file:  # 以写模式打开JSON文件，使用UTF-8编码支持中文字符
        json.dump(system_data, data_file, indent=4, ensure_ascii=False)  # 将数据以JSON格式写入文件，indent=4使输出格式化，ensure_ascii=False支持中文

# 数据加载函数
def load_from_file():
    """从文件加载数据"""
    global user_registry, patient_database, doctor_database, booking_records  # 声明使用全局变量
    try:  # 异常处理结构，尝试执行可能出错的代码块
        with open('hospital_system.json', 'r', encoding='utf-8') as data_file:  # 以读模式打开JSON文件，指定UTF-8编码
            system_data = json.load(data_file)  # 从文件中加载JSON数据并解析为Python字典
            user_registry = system_data.get('user_registry', {})  # 获取用户注册表数据，如果不存在则使用空字典作为默认值
            patient_database = system_data.get('patient_database', {})  # 获取患者数据库，使用get方法避免KeyError异常
            doctor_database = system_data.get('doctor_database', {})  # 获取医生数据库，提供默认空字典
            booking_records = system_data.get('booking_records', [])  # 获取预约记录列表，默认为空列表
    except FileNotFoundError:  # 捕获文件不存在异常，这是程序首次运行时的正常情况
        user_registry = {}  # 如果文件不存在，初始化用户注册表为空字典
        patient_database = {}  # 初始化患者数据库为空字典
        doctor_database = {}  # 初始化医生数据库为空字典
        booking_records = []  # 初始化预约记录为空列表

# 用户认证相关函数
def hash_password(raw_password):
    """密码哈希加密"""
    return hashlib.sha256(raw_password.encode()).hexdigest()  # 使用SHA256算法对密码进行哈希，encode()转为字节，hexdigest()返回十六进制字符串

def create_new_account(login_id, raw_password, user_type, **additional_info):
    """创建新用户账号"""
    if login_id in user_registry:  # 检查用户ID是否已存在于用户注册表中
        raise ValueError('该用户名已被占用')  # 如果用户名已存在，抛出值错误异常，阻止重复注册
    
    encrypted_password = hash_password(raw_password)  # 调用密码哈希方法对原始密码进行加密处理
    account_info = {  # 创建账户信息字典，存储用户的基本认证信息
        'login_id': login_id,  # 用户登录ID
        'encrypted_password': encrypted_password,  # 加密后的密码哈希值
        'user_type': user_type  # 用户类型，区分患者和医生
    }
    
    user_registry[login_id] = account_info  # 将账户信息添加到全局用户注册表中
    
    if user_type == 'patient':  # 如果用户类型是患者，需要创建患者专用的数据记录
        patient_database[login_id] = {  # 在患者数据库中创建该用户的详细信息记录
            'login_id': login_id,  # 登录ID，用于关联用户认证信息
            'encrypted_password': encrypted_password,  # 加密密码，冗余存储确保数据一致性
            'full_name': additional_info['full_name'],  # 从可变参数中获取患者真实姓名
            'patient_age': additional_info['patient_age'],  # 获取患者年龄信息
            'phone_number': additional_info['phone_number'],  # 获取患者联系电话
            'home_address': additional_info['home_address'],  # 获取患者家庭住址
            'patient_bookings': []  # 初始化患者预约列表，新注册用户没有预约记录
        }
    elif user_type == 'doctor':  # 如果用户类型是医生，创建医生专用的数据记录
        doctor_database[login_id] = {  # 在医生数据库中创建该用户的详细信息记录
            'login_id': login_id,  # 医生登录ID
            'encrypted_password': encrypted_password,  # 加密密码
            'full_name': additional_info['full_name'],  # 医生真实姓名
            'medical_specialty': additional_info['medical_specialty'],  # 医生的专业科室
            'availability_calendar': {}  # 医生的出诊时间日历，初始为空字典，键为日期，值为时间段列表
        }
    
    persist_to_file()  # 调用数据持久化方法，将新注册的用户信息保存到文件中

def verify_credentials(login_id, raw_password):
    """验证用户凭据"""
    if login_id not in user_registry:  # 检查用户ID是否存在于用户注册表中
        raise ValueError('用户账号不存在')  # 如果用户不存在，抛出异常提示账号不存在
    
    encrypted_password = hash_password(raw_password)  # 对用户输入的密码进行哈希加密
    stored_password = user_registry[login_id]['encrypted_password']  # 从数据库中获取存储的密码哈希值
    
    if stored_password != encrypted_password:  # 比较存储的密码哈希值与输入密码的哈希值
        raise ValueError('密码验证失败')  # 如果密码不匹配，抛出异常提示验证失败
    
    return user_registry[login_id]  # 验证成功，返回用户的完整信息

# 预约管理相关函数
def generate_booking_id(doctor_id, appointment_date):
    """生成预约编号"""
    same_date_bookings = [  # 使用列表推导式查找同一医生在同一日期的所有预约
        booking for booking in booking_records  # 遍历所有预约记录
        if booking['doctor_id'] == doctor_id and booking['appointment_date'] == appointment_date  # 筛选条件：相同医生ID和相同日期
    ]
    return len(same_date_bookings) + 1  # 返回该日期该医生的预约数量加1作为新的预约编号

def schedule_appointment(patient_id, doctor_id, appointment_date, time_start, time_end):
    """安排预约"""
    if patient_id not in patient_database:  # 验证患者是否存在于患者数据库中
        raise ValueError('患者信息未找到')  # 如果患者不存在，抛出异常
    if doctor_id not in doctor_database:  # 验证医生是否存在于医生数据库中
        raise ValueError('医生信息未找到')  # 如果医生不存在，抛出异常
    
    doctor_info = doctor_database[doctor_id]  # 获取医生的完整信息记录
    time_available = False  # 初始化时间可用性标志为False，假设时间不可用
    
    if appointment_date in doctor_info['availability_calendar']:  # 检查指定日期是否在医生的出诊日历中
        for time_slot in doctor_info['availability_calendar'][appointment_date]:  # 遍历该日期的所有可用时间段
            if time_slot[0] <= time_start and time_slot[1] >= time_end:  # 检查预约时间是否在可用时间段内
                time_available = True  # 标记时间可用
                doctor_info['availability_calendar'][appointment_date].remove(time_slot)  # 从可用时间中移除已被预约的时间段
                
                if time_slot[0] < time_start:  # 如果原时间段开始时间早于预约开始时间
                    doctor_info['availability_calendar'][appointment_date].append((time_slot[0], time_start))  # 添加前段剩余时间
                if time_slot[1] > time_end:  # 如果原时间段结束时间晚于预约结束时间
                    doctor_info['availability_calendar'][appointment_date].append((time_end, time_slot[1]))  # 添加后段剩余时间
                break  # 找到可用时间段后跳出循环
    
    if not time_available:  # 如果没有找到可用时间段
        raise ValueError('该时间段医生不可用')  # 抛出异常提示时间不可用
    
    booking_id = generate_booking_id(doctor_id, appointment_date)  # 生成新的预约编号
    new_booking = {  # 创建新预约记录字典
        'patient_id': patient_id,  # 患者ID
        'doctor_id': doctor_id,  # 医生ID
        'appointment_date': appointment_date,  # 预约日期
        'time_start': time_start,  # 预约开始时间
        'time_end': time_end,  # 预约结束时间
        'booking_id': booking_id  # 预约编号
    }
    
    booking_records.append(new_booking)  # 将新预约添加到全局预约记录列表中
    patient_database[patient_id]['patient_bookings'].append(new_booking)  # 将预约添加到患者的个人预约列表中
    persist_to_file()  # 保存数据到文件确保数据持久化

def remove_appointment(patient_id, doctor_id, appointment_date, time_start, time_end):
    """取消预约"""
    target_booking = None  # 初始化目标预约变量为None
    
    for booking in booking_records:  # 遍历所有预约记录查找目标预约
        if (booking['patient_id'] == patient_id and  # 匹配患者ID
            booking['doctor_id'] == doctor_id and  # 匹配医生ID
            booking['appointment_date'] == appointment_date and  # 匹配预约日期
            booking['time_start'] == time_start and  # 匹配开始时间
            booking['time_end'] == time_end):  # 匹配结束时间
            target_booking = booking  # 找到目标预约记录
            break  # 跳出循环
    
    if target_booking:  # 如果找到了目标预约
        booking_records.remove(target_booking)  # 从全局预约记录中删除该预约
        patient_database[patient_id]['patient_bookings'] = [  # 使用列表推导式从患者预约列表中删除该预约
            booking for booking in patient_database[patient_id]['patient_bookings']  # 遍历患者的所有预约
            if not (booking['doctor_id'] == doctor_id and  # 排除匹配的预约记录
                   booking['appointment_date'] == appointment_date and
                   booking['time_start'] == time_start and
                   booking['time_end'] == time_end)
        ]
        
        doctor_database[doctor_id]['availability_calendar'][appointment_date].append((time_start, time_end))  # 将取消的时间段重新加入医生的可用时间
        persist_to_file()  # 保存数据变更到文件
    else:  # 如果没有找到目标预约
        raise ValueError('未找到对应预约记录')  # 抛出异常提示预约不存在

def get_patient_booking_list(patient_id):
    """获取患者预约列表"""
    return patient_database[patient_id]['patient_bookings']  # 返回该患者的所有预约记录

def get_doctor_booking_list(doctor_id):
    """获取医生预约列表"""
    doctor_bookings = [booking for booking in booking_records if booking['doctor_id'] == doctor_id]  # 筛选该医生的所有预约
    return doctor_bookings, len(doctor_bookings)  # 返回预约列表和预约总数的元组

# 医生排班相关函数
def set_available_time(doctor_id, date, start_time, end_time):
    """设置医生可用时间"""
    if doctor_id not in doctor_database:  # 验证医生是否存在
        raise ValueError('医生信息不存在')  # 如果医生不存在，抛出异常
    
    doctor_info = doctor_database[doctor_id]  # 获取医生信息
    if date not in doctor_info['availability_calendar']:  # 如果该日期不在医生日历中
        doctor_info['availability_calendar'][date] = []  # 为该日期创建空的时间段列表
    
    doctor_info['availability_calendar'][date].append((start_time, end_time))  # 将新的时间段添加到该日期的可用时间列表中
    persist_to_file()  # 保存数据到文件

def get_all_doctors():
    """获取所有医生列表"""
    return [  # 使用列表推导式创建医生信息列表
        {  # 为每个医生创建信息字典
            'login_id': doc['login_id'],  # 医生登录ID
            'full_name': doc['full_name'],  # 医生姓名
            'medical_specialty': doc['medical_specialty']  # 医生专业科室
        }
        for doc in doctor_database.values()  # 遍历医生数据库中的所有医生记录
    ]

# 用户界面相关函数
def display_main_menu():
    """显示主菜单"""
    load_from_file()  # 在系统启动时加载数据文件
    
    while True:  # 无限循环显示主菜单，直到用户选择退出
        print("\n=== 医院预约挂号系统 ===")  # 打印系统标题，\n创建空行增加视觉效果
        print("【1】用户注册")  # 显示注册选项，使用中文方括号增加美观性
        print("【2】用户登录")  # 显示登录选项
        print("【3】系统退出")  # 显示退出选项
        
        user_input = input("请输入您的选择: ")  # 获取用户输入，使用中文提示增加友好性
        
        if user_input == '1':  # 如果用户选择注册功能
            handle_registration()  # 调用注册处理方法
        elif user_input == '2':  # 如果用户选择登录功能
            handle_login()  # 调用登录处理方法
        elif user_input == '3':  # 如果用户选择退出
            print("感谢使用，再见！")  # 显示感谢信息
            break  # 跳出循环，结束程序
        else:  # 如果用户输入无效选项
            print("选择无效，请重新输入")  # 提示用户重新选择

def handle_registration():
    """处理用户注册"""
    user_id = input("设置用户名: ")  # 获取用户想要注册的用户名
    user_password = input("设置密码: ")  # 获取用户设置的密码
    print("选择账户类型: 【1】患者 【2】医生")  # 显示账户类型选项
    type_choice = input("请输入选择: ")  # 获取用户选择的账户类型
    
    try:  # 异常处理结构，捕获注册过程中可能出现的错误
        if type_choice == '1':  # 如果选择注册患者账户
            patient_name = input("请输入真实姓名: ")  # 获取患者真实姓名
            patient_age = input("请输入年龄: ")  # 获取患者年龄
            contact_info = input("请输入联系电话: ")  # 获取患者联系方式
            address_info = input("请输入家庭住址: ")  # 获取患者地址信息
            create_new_account(  # 调用账户创建方法
                user_id, user_password, 'patient',  # 传入基本信息和用户类型
                full_name=patient_name, patient_age=patient_age,  # 传入患者特有信息
                phone_number=contact_info, home_address=address_info
            )
            print("患者账户注册成功！")  # 显示注册成功信息
        elif type_choice == '2':  # 如果选择注册医生账户
            doctor_name = input("请输入真实姓名: ")  # 获取医生真实姓名
            specialty_info = input("请输入专业科室: ")  # 获取医生专业科室
            create_new_account(  # 调用账户创建方法
                user_id, user_password, 'doctor',  # 传入基本信息和用户类型
                full_name=doctor_name, medical_specialty=specialty_info  # 传入医生特有信息
            )
            print("医生账户注册成功！")  # 显示注册成功信息
        else:  # 如果选择了无效的账户类型
            print("账户类型选择错误")  # 提示用户选择错误
    except ValueError as error:  # 捕获值错误异常，如用户名重复等
        print(f"注册失败: {error}")  # 显示具体的错误信息

def handle_login():
    """处理用户登录"""
    user_id = input("请输入用户名: ")  # 获取用户输入的用户名
    user_password = input("请输入密码: ")  # 获取用户输入的密码
    
    try:  # 异常处理结构
        authenticated_user = verify_credentials(user_id, user_password)  # 验证用户凭据
        print(f"登录成功！欢迎 {authenticated_user['login_id']}，您是{authenticated_user['user_type']}")  # 显示登录成功信息
        
        if authenticated_user['user_type'] == 'patient':  # 如果是患者用户
            patient_menu(authenticated_user)  # 进入患者功能菜单
        elif authenticated_user['user_type'] == 'doctor':  # 如果是医生用户
            doctor_menu(authenticated_user)  # 进入医生功能菜单
    except ValueError as error:  # 捕获登录验证失败的异常
        print(f"登录失败: {error}")  # 显示登录失败的具体原因

def patient_menu(user_info):
    """患者功能菜单"""
    while True:  # 循环显示患者菜单直到用户退出
        print("\n=== 患者操作面板 ===")  # 显示患者菜单标题
        print("【1】预约医生")  # 预约功能选项
        print("【2】查看预约")  # 查看预约功能选项
        print("【3】取消预约")  # 取消预约功能选项
        print("【4】退出登录")  # 退出登录选项
        
        choice = input("请选择操作: ")  # 获取用户选择
        
        if choice == '1':  # 如果选择预约医生
            book_appointment(user_info)  # 调用预约方法
        elif choice == '2':  # 如果选择查看预约
            view_patient_appointments(user_info)  # 调用查看预约方法
        elif choice == '3':  # 如果选择取消预约
            cancel_patient_appointment(user_info)  # 调用取消预约方法
        elif choice == '4':  # 如果选择退出登录
            print("退出登录成功")  # 显示退出信息
            break  # 跳出循环返回主菜单
        else:  # 如果选择无效
            print("选择无效，请重新输入")  # 提示用户重新选择

def doctor_menu(user_info):
    """医生功能菜单"""
    while True:  # 循环显示医生菜单
        print("\n=== 医生操作面板 ===")  # 显示医生菜单标题
        print("【1】设置出诊时间")  # 设置出诊时间选项
        print("【2】查看预约情况")  # 查看预约情况选项
        print("【3】退出登录")  # 退出登录选项
        
        choice = input("请选择操作: ")  # 获取用户选择
        
        if choice == '1':  # 如果选择设置出诊时间
            set_doctor_schedule(user_info)  # 调用设置出诊时间方法
        elif choice == '2':  # 如果选择查看预约情况
            view_doctor_appointments(user_info)  # 调用查看预约方法
        elif choice == '3':  # 如果选择退出登录
            print("退出登录成功")  # 显示退出信息
            break  # 跳出循环
        else:  # 如果选择无效
            print("选择无效，请重新输入")  # 提示重新选择

def book_appointment(user_info):
    """预约挂号"""
    available_doctors = get_all_doctors()  # 获取所有可用医生列表
    doctor_table = PrettyTable(["序号", "医生账号", "医生姓名", "专业科室"])  # 创建美观的表格显示医生信息
    
    for index, doctor in enumerate(available_doctors):  # 遍历医生列表，enumerate提供索引
        doctor_table.add_row([  # 向表格添加一行医生信息
            index + 1, doctor['login_id'],  # 序号从1开始，医生账号
            doctor['full_name'], doctor['medical_specialty']  # 医生姓名和专业科室
        ])
    
    print("可预约医生列表：")  # 显示表格标题
    print(doctor_table)  # 打印格式化的医生信息表格
    
    try:  # 异常处理结构
        doctor_index = int(input("请选择医生序号: ")) - 1  # 获取用户选择的医生序号并转换为索引
        selected_doctor = available_doctors[doctor_index]['login_id']  # 根据索引获取选中医生的ID
        booking_date = input("请输入预约日期（格式：YYYY-MM-DD）: ")  # 获取预约日期
        start_time = input("请输入开始时间（格式：HH:MM）: ")  # 获取预约开始时间
        end_time = input("请输入结束时间（格式：HH:MM）: ")  # 获取预约结束时间
        
        schedule_appointment(  # 调用预约安排方法
            user_info['login_id'], selected_doctor,  # 传入患者ID和医生ID
            booking_date, start_time, end_time  # 传入预约的日期和时间信息
        )
        print("预约成功！")  # 显示预约成功信息
    except (ValueError, IndexError) as error:  # 捕获值错误和索引错误异常
        print(f"预约失败: {error}")  # 显示预约失败的具体原因

def view_patient_appointments(user_info):
    """查看患者预约"""
    appointment_list = get_patient_booking_list(user_info['login_id'])  # 获取该患者的所有预约记录
    appointment_table = PrettyTable(["医生账号", "预约日期", "开始时间", "结束时间", "预约号"])  # 创建预约信息表格
    
    for appointment in appointment_list:  # 遍历患者的每个预约记录
        appointment_table.add_row([  # 向表格添加预约信息行
            appointment['doctor_id'], appointment['appointment_date'],  # 医生ID和预约日期
            appointment['time_start'], appointment['time_end'],  # 开始时间和结束时间
            appointment['booking_id']  # 预约编号
        ])
    
    print("您的预约记录：")  # 显示表格标题
    print(appointment_table)  # 打印格式化的预约信息表格

def cancel_patient_appointment(user_info):
    """取消患者预约"""
    try:  # 异常处理结构
        doctor_id = input("请输入医生账号: ")  # 获取要取消预约的医生账号
        appointment_date = input("请输入预约日期（格式：YYYY-MM-DD）: ")  # 获取要取消的预约日期
        start_time = input("请输入开始时间（格式：HH:MM）: ")  # 获取要取消的预约开始时间
        end_time = input("请输入结束时间（格式：HH:MM）: ")  # 获取要取消的预约结束时间
        
        remove_appointment(  # 调用取消预约方法
            user_info['login_id'], doctor_id,  # 传入患者ID和医生ID
            appointment_date, start_time, end_time  # 传入预约的具体时间信息
        )
        print("取消预约成功！")  # 显示取消成功信息
    except ValueError as error:  # 捕获取消预约过程中的异常
        print(f"取消失败: {error}")  # 显示取消失败的具体原因

def set_doctor_schedule(user_info):
    """设置医生出诊时间"""
    try:  # 异常处理结构
        available_date = input("请输入出诊日期（格式：YYYY-MM-DD）: ")  # 获取医生的出诊日期
        start_time = input("请输入开始时间（格式：HH:MM）: ")  # 获取出诊开始时间
        end_time = input("请输入结束时间（格式：HH:MM）: ")  # 获取出诊结束时间
        
        set_available_time(  # 调用设置可用时间方法
            user_info['login_id'], available_date, start_time, end_time  # 传入医生ID和时间信息
        )
        print("出诊时间设置成功！")  # 显示设置成功信息
    except ValueError as error:  # 捕获设置过程中的异常
        print(f"设置失败: {error}")  # 显示设置失败的具体原因

def view_doctor_appointments(user_info):
    """查看医生预约"""
    appointment_list, total_count = get_doctor_booking_list(user_info['login_id'])  # 获取医生的预约列表和总数
    appointment_table = PrettyTable(["患者账号", "预约日期", "开始时间", "结束时间", "预约号"])  # 创建预约信息表格
    
    for appointment in appointment_list:  # 遍历医生的每个预约记录
        appointment_table.add_row([  # 向表格添加预约信息行
            appointment['patient_id'], appointment['appointment_date'],  # 患者ID和预约日期
            appointment['time_start'], appointment['time_end'],  # 开始时间和结束时间
            appointment['booking_id']  # 预约编号
        ])
    
    print(f"您的预约情况（共计 {total_count} 个预约）：")  # 显示预约总数统计
    print(appointment_table)  # 打印格式化的预约信息表格

# 启动医院预约系统的主函数
def run_hospital_system():
    """启动医院预约系统"""
    display_main_menu()  # 调用主菜单显示方法，开始程序执行

if __name__ == "__main__":  # Python程序入口判断，确保只有直接运行该文件时才执行主程序
    run_hospital_system()  # 调用系统启动函数，开始整个医院预约系统的运行